package com.guang.service;

import com.alibaba.fastjson.JSON;
import com.guang.common.RedisKeyPrefixConst;
import com.guang.common.RedisUtil;
import com.guang.dao.ProductDao;
import com.guang.model.Product;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 1、数据库缓存不一致问题！！！！ 分布式锁来解决：但是需要注意的是分布式锁对于key的力度问题！！！！
 * 对于分布式锁针对于读多写少的场景，可以来对分布式锁来进行优化 ----->读和读是并发的！！！！ 但是读和写、写和写是互斥的
 */
@Service
public class ProductService4 {

    @Autowired
    private ProductDao productDao;

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private Redisson redisson;

    public static final Integer PRODUCT_CACHE_TIMEOUT = 60 * 60 * 24;
    private static final String LOCK_PRODUCT_HOT_CACHE_PREFIX = "lock:product:hot_cache:";
    private static final String LOCK_PRODUCT_UPDATE_QUERY_CACHE_PREFIX = "lock:product:query:cache:";
    public static final String EMPTY_CACHE = "{}";
    public static Map<String, Product> productMap = new ConcurrentHashMap<>();

    /**
     * 创建商品
     *
     * @param product
     * @return
     */
    @Transactional
    public Product create(Product product) {
        Product productResult = productDao.create(product);
        redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult));
        return productResult;
    }

    /**
     * 简单使用过程中可能会出现的问题！
     *
     * @param product
     * @return
     */
    @Transactional
    public Product update(Product product) {
        Product productResult = null;
//        RLock productBasicIdLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_PREFIX + product.getId());
//        productBasicIdLock.lock();
        RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_HOT_CACHE_PREFIX + product.getId());
        RLock rLock = readWriteLock.readLock();
        try {
            productResult = productDao.update(product);
            redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult));
            // 更新的时候设置一下随机时间！
            redisUtil.expire(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), genProductCacheTimeout(), TimeUnit.SECONDS);
        } finally {
            rLock.unlock();
        }

        return productResult;
    }

    /**
     * 根据商品ID来进行查询对应的商品
     *
     * @param productId 商品ID
     * @return 返回缓存中的商品
     * @throws InterruptedException
     */
    public Product get(Long productId) throws InterruptedException {
        Product product = null;
        String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId;
        // 首先从Redis中来查询得到缓存对象
        product = getProductByCacheKeyFromCache(productCacheKey);
        if (product != null) {
            return product;
        }
        // TODO ： 这里应该来做分布式锁来进行实现！---------->>>>> 重建缓存！！！！！！！
        RLock productBasicIdLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_PREFIX + productId);
        productBasicIdLock.lock();
        try {
            // 首先从Redis中来查询得到缓存对象
            product = getProductByCacheKeyFromCache(productCacheKey);
            if (product != null) {
                return product;
            }
            // 解决缓存和数据库不一致的问题！！！
//            RLock updateQueryLock = redisson.getLock(LOCK_PRODUCT_UPDATE_QUERY_CACHE_PREFIX);
//            updateQueryLock.lock();
            RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_HOT_CACHE_PREFIX + productId);
            RLock rLock = readWriteLock.readLock();
            rLock.lock();
            try {
                product = productDao.get(productId);
                if (product != null) {
                    redisUtil.set(productCacheKey, JSON.toJSONString(product), PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
                } else {
                    redisUtil.set(productCacheKey, EMPTY_CACHE, 60 * 2, TimeUnit.SECONDS);
                }
            } finally {
                rLock.unlock();
            }
        } finally {
            productBasicIdLock.unlock();
        }
        return product;
    }


    private Integer genProductCacheTimeout() {
        return PRODUCT_CACHE_TIMEOUT + new Random().nextInt(5) * 60 * 60;
    }

    private Integer genEmptyCacheTimeout() {
        return 60 + new Random().nextInt(30);
    }

    private Product getProductFromCache(String productCacheKey) {
        Product product = productMap.get(productCacheKey);
        if (product != null) {
            return product;
        }

        String productStr = redisUtil.get(productCacheKey);
        if (!StringUtils.isEmpty(productStr)) {
            if (EMPTY_CACHE.equals(productStr)) {
                redisUtil.expire(productCacheKey, genEmptyCacheTimeout(), TimeUnit.SECONDS);
                return new Product();
            }
            product = JSON.parseObject(productStr, Product.class);
            redisUtil.expire(productCacheKey, genProductCacheTimeout(), TimeUnit.SECONDS); //读延期
        }
        return product;
    }

    private Product getProductByCacheKeyFromCache(String cacheKey) {
        Product product = null;
        String productStr = redisUtil.get(cacheKey);
        if (!StringUtils.isEmpty(productStr)) {
            if (EMPTY_CACHE.equals(productStr)) {
                redisUtil.expire(cacheKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
                // 区分到底是外界伪造的还是本地中实际就不存在的！
                return new Product();
            }
            product = JSON.parseObject(productStr, Product.class);
            // 每次查询的时候都可以给来当前key设置一下时间，防止当前缓存失效-------->实现了简单的冷热分离！！！
            redisUtil.expire(cacheKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
        }
        return product;
    }

}